import { Injectable } from '@angular/core';
import JSZip from 'jszip';
import { srtm_labels } from './srtm-labels';

const ARRAY_LEN = 1201 * 1201;
// 原始数据空值
const VOID_VAL = -32768;
// 特殊高程值设置
const ZERO_VALUE = 0.1;  // 原始数据为0、空值，或者下载和计算过程中出错时（除网络因素），设置为0.01
const ERROR_NETWORK = -1001;  // 网络错误


@Injectable({
    providedIn: 'root'
})
export class SrtmService {
    srtm_cache = new Map();

    gen_url(srtm_label: string) {
        return `http://www.modelscope.cn/api/v1/datasets/huolongshe/srtm/repo?FilePath=${srtm_label}.hgt.zip`;
    }

    async get_alt(lon: number, lat: number) {
        const lon_i = Math.floor(lon);
        const lat_i = Math.floor(lat);
        const srtm_label = `N${lat_i.toString().padStart(2, '0')}E${lon_i.toString().padStart(3, '0')}`;

        // 中国大陆以外
        if (!srtm_labels.includes(srtm_label)) {
            return ZERO_VALUE;
        }

        let srtm_data = this.srtm_cache.get(srtm_label);

        if (!srtm_data) {
            try {
                srtm_data = await this.download_srtm(srtm_label);
                this.srtm_cache.set(srtm_label, srtm_data);
            } catch (err) {
                return ERROR_NETWORK;
            }
        }

        const alt = this.compute_alt_usgs(lon, lat, srtm_data);
        return parseFloat(alt.toFixed(2));
    }

    async download_srtm(srtm_label: string) {
        const url = this.gen_url(srtm_label);

        let zip_array_buffer;
        try {
            const response = await fetch(url);

            if (!response.ok) {
                throw new Error('Server error');
            }

            zip_array_buffer = await response.arrayBuffer();
        } catch (err) {
            throw new Error('Network error');
        }

        const zip = await JSZip.loadAsync(zip_array_buffer);

        // 找到 .hgt 文件（通常只有一个）
        const hgt_file = Object.values(zip.files).find(file => file.name.endsWith('.hgt'));
        if (!hgt_file) {
          throw new Error("未找到 .hgt 文件");
        }

        // 提取 .hgt 为 ArrayBuffer
        const hgt_array_buffer = await hgt_file.async('arraybuffer');
        const data_view = new DataView(hgt_array_buffer);

        const srtm_data = new Array(ARRAY_LEN).fill(0);

        for (let i = 0; i < ARRAY_LEN; i++) {
            srtm_data[i] = data_view.getInt16(i * 2, false); // false = big-endian
            if (srtm_data[i] >= 32768) {
                srtm_data[i] -= 65536;
            }
        }

        return srtm_data
    }

    compute_alt_usgs(lon: number, lat: number, alts: number[]) {
        const x = (lon - Math.floor(lon)) * 1200;
        const y = 1200.9999999 - (lat - Math.floor(lat)) * 1200;  // 不使用1201，以解决纬度坐标正好等于整数边界坐标的问题
        const idx1 = Math.floor(y) * 1201 + Math.floor(x);
        const four_alts = [alts[idx1], alts[idx1 + 1], alts[idx1 - 1201], alts[idx1 - 1200]];
        let num_voids = 0;
        for (const alt of four_alts) {
            if (alt === VOID_VAL) {
                num_voids++;
            }
        }

        let alt;
        if (num_voids === 0) {
            alt = this.bilinear_interpolate(four_alts, x, y);
        } else if (num_voids === 1) {
            alt = this.bilinear_interpolate(this.fix_void(four_alts), x, y);
        } else if (num_voids === 2 || num_voids === 3) {
            alt = this.average_non_void(four_alts);
        } else {
            alt = VOID_VAL;
        }

        if (alt <= 0) {
            alt = ZERO_VALUE;  // 计算出的高度无效时，人为设置一个有效的值
        }

        return alt;
    }

    bilinear_interpolate(four_alts: number[], x: number, y: number) {
        const alpha = x - Math.floor(x);
        const beta = 1 - (y - Math.floor(y));
        const alt = (1 - alpha) * (1 - beta) * four_alts[0] + alpha * (1 - beta) * four_alts[1]
              + (1 - alpha) * beta * four_alts[2] + alpha * beta * four_alts[3];
        return alt;
    }

    fix_void(four_alts: number[]) {
        const fixed = [];
        for (const alt of four_alts) {
            if (alt === VOID_VAL) {
                fixed.push(Math.floor(Math.round(this.average_non_void(four_alts))))
            } else {
                fixed.push(alt);
            }
        }
        return fixed
    }

    average_non_void(four_alts: number[]) {
        let total_alt = 0;
        let num_alts = 0;
        for (const alt of four_alts) {
            if (alt !== VOID_VAL) {
                total_alt += alt;
                num_alts += 1;
            }
        }

        if (num_alts < 1) return VOID_VAL;

        return Math.floor(total_alt / num_alts);
    }

}
